#ifndef FILESTORAGE_CPP_
#define FILESTORAGE_CPP_

#include "filestorage.hpp"
#include <thread>
#include <string>

using std::thread;
using std::string;
using std::make_pair;
using std::lock_guard;
using std::mutex;

#define PAGE_SIZE 4096
#define MAX_PAGE_SIZE 67108864
#define MAX_SWAP_MEM 2147483648

FileStorage::FileStorage(size_t size, const string& path)
	: 	block_size_(MAX_PAGE_SIZE > size ? (((size - 1) / PAGE_SIZE) + 1) * PAGE_SIZE : MAX_PAGE_SIZE), 
		data_size_(size), 
		data_free_(size)
{
	swap_ = new MappableFile(path, size, block_size_);
	block_num_ = (size - 1) / block_size_ + 1;
	block_act_ = MAX_SWAP_MEM / MAX_PAGE_SIZE;
	
	for (size_t i = 0; i < block_num_; i++) {
		blocks_.push_back({nullptr, 0});
		holes_.insert(make_pair(block_size_, BlockAddr({i, 0, block_size_})));
	}	
}

FileStorage::~FileStorage()
{
	for(auto chunk_pair : chunks_)
		delete chunk_pair.first;
	
	for(auto& block : blocks_)
		if(block.data)
			swap_->memUnmap(block.data);
	delete swap_;
}

DataChunk* FileStorage::alloc(size_t size)
{	
	if (data_free_ < size) {
		LOG_ERROR("no enough mem");
		throw MemAllocException(MemAllocException::E_NOMEM);
	}
	
	DataChunk* chunk = new DataChunk(this, size);
	
	// FIXME!!!: I think you need upper_bound
	auto hole = holes_.lower_bound(chunk->size_); // FYI: You miss a chance to remove
	// from swap something that stored in Mem.
	if (hole == holes_.end()) {
		LOG_ERROR("too fragmented mem ");
		throw MemAllocException(MemAllocException::E_NOBUFS);
	}
	BlockAddr addr = (*hole).second;
	if (addr.size > size) // FYI: According C++11 you may write {...} instead of BlockAddr({...})
		holes_.insert(make_pair(addr.size - size, BlockAddr({addr.block, addr.pos + size, addr.size - size})));
	appendChunkPair(chunk, BlockAddr({addr.block, addr.pos, size}));
	holes_.erase(hole);
	
	data_free_-= size; // FYI: I think that data_free is useless. Thanks to fragmentation
	
	return chunk;
}

void FileStorage::free(DataChunk* chunk)
{
	/*
	if (chunk->locked_) {
		LOG_ERROR("try free locked mem");
		throw std::exception();
	}
	*/
	// FIXME: And BlockAddr copied again
	auto chunk_pair = getChunkPair(chunk);
	// FIXME: What will be if chunk is invaid?
	
	holes_.insert(make_pair(chunk->size_, chunk_pair->second)); // FIXME: And again
	
	removeChunkPair(chunk);//change to *chunk_pair // FIXME: Let's change)
	
	data_free_ += chunk->size_;
	delete chunk;
}

char* FileStorage::get(DataChunk* chunk)
{	
	auto chunk_pair = getChunkPair(chunk);
	
	// FIXME: Suppose chunk_pair is incorrect. According manual:
	// "If k does not match the key of any element in the container, the function inserts a new element with that key and returns a reference to its mapped value."
	// Is it reallly what you want?  Use map::At to throw an exception in this case
	Block& block = blocks_[chunk_pair->second.block];
	
	if (block.data == nullptr) {
		--block_act_; // FIXME: WTF???
		if(!block_act_) {
			LOG_ERROR("PANIC IN SAWP!!!");
			throw MemAllocException(MemAllocException::E_NOBUFS);
		}
		block.data = reinterpret_cast<char*>(swap_->memMap(chunk_pair->second.block * block_size_));
	}

	++block.locked;
	return chunk->data_ =  block.data + chunk_pair->second.pos;
}

void FileStorage::release(DataChunk* chunk)
{
	auto chunk_pair = getChunkPair(chunk);
	
	Block& block = blocks_[chunk_pair->second.block];
	--block.locked;
	if (!block.locked && !block_act_) {
		for (auto other_pair : chunks_)
			if (other_pair.second.block == chunk_pair->second.block)
				other_pair.first->data_ = nullptr;
				
		swap_->memUnmap(block.data);
		++block_act_;
		block.data = nullptr;
	}
}
#endif // FILESTORAGE_CPP_
